﻿using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;

namespace Notoric.Gui.Controls.Adorners
{
    /// <summary>
    /// Implements a <see cref="Thumb"/> that can be used to drag an <see cref="FrameworkElement"/> on a canvas.
    /// </summary>
    /// <remarks>
    /// <para>
    /// The control expects the moved element to be set as the <see cref="FrameworkElement.DataContext"/>.
    /// </para>
    /// <para>
    /// This code was taken from the "WPF Diagram Designer: Part 1" article on CodeProject.com written by "sukram" and adapted.
    /// See <a href="http://www.codeproject.com/KB/WPF/WPFDiagramDesigner_Part1.aspx">http://www.codeproject.com/KB/WPF/WPFDiagramDesigner_Part1.aspx</a>
    /// </para>
    /// </remarks>
    public class MoveThumb : Thumb
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="MoveThumb"/> instance.
        /// </summary>
        public MoveThumb()
        {
            DragStarted += new DragStartedEventHandler(this.MoveThumb_DragStarted);
            DragDelta += new DragDeltaEventHandler(this.MoveThumb_DragDelta);
        }

        #endregion

        #region Event Handlers

        /// <summary>
        /// Occurs when the user starts to drag this thumb.
        /// </summary>
        /// <remarks>
        /// <b>Operation:</b><br/>
        /// The handler stores a reference to the moved item from the <see cref="FrameworkElement.DataContext"/> property to the <see cref="movedItem"/> field
        /// and stores the rotate transformation of the moved item if any.
        /// </remarks>
        /// <param name="sender">Object on which the event handler is attached</param>
        /// <param name="e">Event data</param>
        private void MoveThumb_DragStarted(object sender, DragStartedEventArgs e)
        {
            movedItem = DataContext as FrameworkElement;

            if (movedItem != null)
            {
                rotateTransform = movedItem.RenderTransform as RotateTransform;
            }
        }

        /// <summary>
        /// Occurs while the thumb is dragged.
        /// </summary>
        /// <remarks>
        /// <b>Operation:</b><br/>
        /// The handler updates the left and top coordinates of the dragged item and if necessary updates the rotate transformation.
        /// </remarks>
        /// <param name="sender">Object on which the event handler is attached</param>
        /// <param name="e">Event data</param>
        private void MoveThumb_DragDelta(object sender, DragDeltaEventArgs e)
        {
            if (movedItem != null)
            {
                Point dragDelta = new Point(e.HorizontalChange, e.VerticalChange);

                if (rotateTransform != null)
                {
                    dragDelta = rotateTransform.Transform(dragDelta);
                }

                Canvas.SetLeft(movedItem, Canvas.GetLeft(movedItem) + dragDelta.X);
                Canvas.SetTop(movedItem, Canvas.GetTop(movedItem) + dragDelta.Y);
            }
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// Holds the a reference to the rotate transformation of the dragged item.
        /// </summary>
        private RotateTransform rotateTransform;
        /// <summary>
        /// Holds a reference to the dragged item.
        /// </summary>
        private FrameworkElement movedItem;

        #endregion
    }

    /// <summary>
    /// Implements a <see cref="Thumb"/> that can be used to resize a <see cref="FrameworkElement"/> on a canvas.
    /// </summary>
    /// <remarks>
    /// <para>
    /// The control expects the resized element to be set as the <see cref="FrameworkElement.DataContext"/>.
    /// </para>
    /// <para>
    /// This code was taken from the "WPF Diagram Designer: Part 1" article on CodeProject.com written by "sukram" and adapted.
    /// See <a href="http://www.codeproject.com/KB/WPF/WPFDiagramDesigner_Part1.aspx">http://www.codeproject.com/KB/WPF/WPFDiagramDesigner_Part1.aspx</a>
    /// </para>
    /// </remarks>
    public class ResizeThumb : Thumb
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="ResizeThumb"/> instance.
        /// </summary>
        public ResizeThumb()
        {
            DragStarted += new DragStartedEventHandler(ResizeThumb_DragStarted);
            DragDelta += new DragDeltaEventHandler(ResizeThumb_DragDelta);
            DragCompleted += new DragCompletedEventHandler(ResizeThumb_DragCompleted);
        }

        #endregion

        #region Event Handlers

        private void ResizeThumb_DragStarted(object sender, DragStartedEventArgs e)
        {
            resizedItem = DataContext as FrameworkElement;

            if (resizedItem != null)
            {
                var pe = VisualTreeHelper.GetParent(resizedItem);
                while (pe != null && !(pe is Canvas))
                    pe = VisualTreeHelper.GetParent(pe);
                canvas = pe as Canvas;

                if (canvas != null)
                {
                    transformOrigin = resizedItem.RenderTransformOrigin;

                    rotateTransform = resizedItem.RenderTransform as RotateTransform;
                    if (rotateTransform != null)
                    {
                        angle = rotateTransform.Angle * Math.PI / 180.0;
                    }
                    else
                    {
                        angle = 0.0d;
                    }

                    AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(canvas);
                    if (adornerLayer != null)
                    {
                        adorner = new SizeAdorner(resizedItem);
                        adornerLayer.Add(adorner);
                    }
                }
            }
        }

        private void ResizeThumb_DragDelta(object sender, DragDeltaEventArgs e)
        {
            if (resizedItem != null)
            {
                double deltaVertical, deltaHorizontal;

                switch (VerticalAlignment)
                {
                    case System.Windows.VerticalAlignment.Bottom:
                        deltaVertical = Math.Min(-e.VerticalChange, resizedItem.ActualHeight - resizedItem.MinHeight);
                        Canvas.SetTop(resizedItem, Canvas.GetTop(resizedItem) + (transformOrigin.Y * deltaVertical * (1 - Math.Cos(-angle))));
                        Canvas.SetLeft(resizedItem, Canvas.GetLeft(resizedItem) - deltaVertical * transformOrigin.Y * Math.Sin(-angle));
                        resizedItem.Height -= deltaVertical;
                        break;
                    case System.Windows.VerticalAlignment.Top:
                        deltaVertical = Math.Min(e.VerticalChange, resizedItem.ActualHeight - resizedItem.MinHeight);
                        Canvas.SetTop(resizedItem, Canvas.GetTop(resizedItem) + deltaVertical * Math.Cos(-angle) + (transformOrigin.Y * deltaVertical * (1 - Math.Cos(-angle))));
                        Canvas.SetLeft(resizedItem, Canvas.GetLeft(resizedItem) + deltaVertical * Math.Sin(-angle) - (transformOrigin.Y * deltaVertical * Math.Sin(-angle)));
                        resizedItem.Height -= deltaVertical;
                        break;
                    default:
                        break;
                }

                switch (HorizontalAlignment)
                {
                    case System.Windows.HorizontalAlignment.Left:
                        deltaHorizontal = Math.Min(e.HorizontalChange, resizedItem.ActualWidth - resizedItem.MinWidth);
                        Canvas.SetTop(resizedItem, Canvas.GetTop(resizedItem) + deltaHorizontal * Math.Sin(angle) - transformOrigin.X * deltaHorizontal * Math.Sin(angle));
                        Canvas.SetLeft(resizedItem, Canvas.GetLeft(resizedItem) + deltaHorizontal * Math.Cos(angle) + (transformOrigin.X * deltaHorizontal * (1 - Math.Cos(angle))));
                        resizedItem.Width -= deltaHorizontal;
                        break;
                    case System.Windows.HorizontalAlignment.Right:
                        deltaHorizontal = Math.Min(-e.HorizontalChange, resizedItem.ActualWidth - resizedItem.MinWidth);
                        Canvas.SetTop(resizedItem, Canvas.GetTop(resizedItem) - transformOrigin.X * deltaHorizontal * Math.Sin(angle));
                        Canvas.SetLeft(resizedItem, Canvas.GetLeft(resizedItem) + (deltaHorizontal * transformOrigin.X * (1 - Math.Cos(angle))));
                        resizedItem.Width -= deltaHorizontal;
                        break;
                    default:
                        break;
                }
            }

            e.Handled = true;
        }

        private void ResizeThumb_DragCompleted(object sender, DragCompletedEventArgs e)
        {
            if (adorner != null)
            {
                AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(canvas);
                if (adornerLayer != null)
                {
                    adornerLayer.Remove(adorner);
                }

                adorner = null;
            }
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// Holds a reference to the rotation transformation of the resized item.
        /// </summary>
        private RotateTransform rotateTransform;
        /// <summary>
        /// Holds the angle of the rotation.
        /// </summary>
        private double angle;
        /// <summary>
        /// Holds a reference to the size adorner.
        /// </summary>
        private Adorner adorner;
        /// <summary>
        /// Holds the coordinates of the transform origin.
        /// </summary>
        private Point transformOrigin;
        /// <summary>
        /// Holds a reference to the control that is being resized.
        /// </summary>
        private FrameworkElement resizedItem;
        /// <summary>
        /// Holds a reference to the canvas that owns the resized control.
        /// </summary>
        private Canvas canvas;

        #endregion
    }

    /// <summary>
    /// Implements a <see cref="Thumb"/> that can be used to rotate a <see cref="FrameworkElement"/> on a canvas.
    /// </summary>
    /// <remarks>
    /// <para>
    /// The control expects the rotated element to be set as the <see cref="FrameworkElement.DataContext"/>.
    /// </para>
    /// <para>
    /// This code was taken from the "WPF Diagram Designer: Part 1" article on CodeProject.com written by "sukram" and adapted.
    /// See <a href="http://www.codeproject.com/KB/WPF/WPFDiagramDesigner_Part1.aspx">http://www.codeproject.com/KB/WPF/WPFDiagramDesigner_Part1.aspx</a>
    /// </para>
    /// </remarks>
    public class RotateThumb : Thumb
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="RotateThumb"/> instance.
        /// </summary>
        public RotateThumb()
        {
            DragDelta += new DragDeltaEventHandler(this.RotateThumb_DragDelta);
            DragStarted += new DragStartedEventHandler(this.RotateThumb_DragStarted);
        }

        #endregion

        #region Event Handlers

        private void RotateThumb_DragStarted(object sender, DragStartedEventArgs e)
        {
            rotatedItem = DataContext as FrameworkElement;

            if (rotatedItem != null)
            {
                var pe = VisualTreeHelper.GetParent(rotatedItem);
                while (pe != null && !(pe is Canvas))
                    pe = VisualTreeHelper.GetParent(pe);
                canvas = pe as Canvas;

                if (canvas != null)
                {
                    centerPoint = rotatedItem.TranslatePoint(
                        new Point(rotatedItem.Width * rotatedItem.RenderTransformOrigin.X,
                                  rotatedItem.Height * rotatedItem.RenderTransformOrigin.Y),
                                  canvas);

                    Point startPoint = Mouse.GetPosition(canvas);
                    startVector = Point.Subtract(startPoint, centerPoint);

                    rotateTransform = rotatedItem.RenderTransform as RotateTransform;
                    if (rotateTransform == null)
                    {
                        rotatedItem.RenderTransform = new RotateTransform(0);
                        initialAngle = 0;
                    }
                    else
                    {
                        initialAngle = rotateTransform.Angle;
                    }
                }
            }
        }

        private void RotateThumb_DragDelta(object sender, DragDeltaEventArgs e)
        {
            if (rotatedItem != null && canvas != null)
            {
                Point currentPoint = Mouse.GetPosition(canvas);
                Vector deltaVector = Point.Subtract(currentPoint, centerPoint);

                double angle = Vector.AngleBetween(startVector, deltaVector);

                RotateTransform rotateTransform = rotatedItem.RenderTransform as RotateTransform;
                rotateTransform.Angle = initialAngle + Math.Round(angle, 0);
                rotatedItem.InvalidateMeasure();
            }
        }

        #endregion

        #region Private Fields

        private double initialAngle;
        private RotateTransform rotateTransform;
        private Vector startVector;
        private Point centerPoint;
        private FrameworkElement rotatedItem;
        private Canvas canvas;

        #endregion
    }
}